use std::io::stdout;

/// 《Rust 程序设计语言》第四章 什么是所有权？
fn main() {
    // 栈
    /// 栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据，要改为存储在堆上。
    /// 当你的代码调用一个函数时，传递给函数的值（包括可能指向堆上数据的指针）和函数的局部变量被压入栈中。
    /// 当函数结束时，这些值被移出栈。

    // 堆
    /// 堆是缺乏组织的：当向堆放入数据时，你要请求一定大小的空间。
    /// 内存分配器（memory allocator）在堆的某处找到一块足够大的空位，把它标记为已使用，并返回一个表示该位置地址的 指针（pointer）。
    /// 这个过程称作 在堆上分配内存（allocating on the heap），有时简称为 “分配”（allocating）。（将数据推入栈中并不被认为是分配）。
    /// 因为指向放入堆中数据的指针是已知的并且大小是固定的，你可以将该指针存储在栈上，不过当需要实际数据时，必须访问指针。
    println!("《堆》: Hello, world!");

    // 所有权规则
    /// 1.Rust 中的每一个值都有一个 所有者（owner）。
    /// 2.值在任一时刻有且只有一个所有者。
    /// 3.当所有者（变量）离开作用域，这个值将被丢弃。
    let s = String::from("hello");

    // 内存与分配
    /// 1.必须在运行时向内存分配器（memory allocator）请求内存。
    /// 2.需要一个当我们处理完 String 时将内存返回给分配器的方法。
    /// 第一部分由我们完成：当调用 String::from 时，它的实现 (implementation) 请求其所需的内存。这在编程语言中是非常通用的。
    /// 第二部分在rust语言中我们需要精确的为一个 allocate（分配） 配对一个 free（释放）。为了避免以下三种事情：
    /// ①忘记回收了会浪费内存。②如果过早回收了，将会出现无效变量。③如果重复回收，这也是个 bug。
    /// 但是！！当有多个变量使用在堆上分配的内存时，这有时是非常复杂的！！！下面我们来探讨这样的场景。

    // 变量与数据的交互方式（本质是栈中变量与堆中数据的交互）
    // 1.移动
    /// 大致可以猜到这在干什么：“将 5 绑定到 x；接着生成一个值 x 的拷贝并绑定到 y”。
    /// 现在有了两个变量，x 和 y，都等于 5。
    /// 这也正是事实上发生了的，因为整数是有已知固定大小的简单值，所以这两个 5 被放入了栈中。
    let x = 5;
    let y = x;
    /// 现在看看这个 String 版本：
    let s1 = String::from("hello");
    let s2 = s1;
    /// 第二行会生成一个s1的拷贝并绑定到s2上吗？事实上情况有些不同。
    /// 变量s1在栈中的结构：一个指向存放字符串内容内存的指针（指向堆区），一个长度，和一个容量。
    /// 将 s1 赋值给 s2，这意味着我们从栈上拷贝了它的指针、长度和容量。我们并没有复制指针指向的堆上的数据。
    /// 如果轻易的就拷贝堆区的数据会对运行时性能造成非常大的影响。
    /// 所以 s1 和 s2，指向同一个字符串内容内存。但这就又延伸出一个问题："内存的二次释放bug"（前面提到过当变量离开作用域时会释放堆区内存）
    /// s1，s2 都离开作用域，内存将会被释放两次。所以为了确保内存安全，在 let s2 = s1; 之后，Rust 认为 s1 不再有效，因此 Rust 不需要在 s1 离开作用域后清理任何东西。
    /// 所以在 s2 被创建之后，不能再操作s1，否则编译器将会报错。
    /// 另外，这里还隐含了一个设计选择：Rust 永远也不会自动创建数据的 “深拷贝”。
    /// 因此，您可以认为再 Rust 中任何 "自动" 的复制都是对运行时性能影响较小的。
    // 2.克隆
    let s1 = String::from("hello");
    let s2 = s1.clone();
    let len1 = s1.len();
    let len2 = s2.len();
    println!("《移动(move)与克隆(clone)》: len1 = {}, len2 = {}", len1, len2);


    // 变量与函数的交互方式（本质是栈中变量与栈中函数的交互）
    let s = String::from("hello");  /// s 进入作用域
    takes_ownership(s);  /// s 的值被移动(move)到函数中...
    // ... 因此在这里s不再有效
    let x = 5;  /// x 进入作用域
    makes_copy(x);  /// 因为 i32 实现了 Copy trait，
    // x 不会移动到函数中，
    println!("《变量与函数交互之基本类型的copy》: {}", x); /// 所以之后仍然可以使用 x



    // 引用与借用
    /// 我们将创建引用的行为称为借用。引用没有值的所有权。
    /// 在任意给定时间，要么 只能有一个可变引用，要么 只能有多个不可变引用。
    /// 引用必须总是有效的。
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("《引用与借用》: The length of '{}' is {}.", s1, len);


    // slice 切片类型
    // slice 允许你引用集合中一段连续的元素序列，而不用引用整个集合。slice 是一种引用，所以它没有所有权。


}

fn calculate_length(s: &String) -> usize {
    /// s 是 String 的引用
    s.len()
}  // 这里, s 离开了作用域。但因为它并不拥有引用值的所有权，所以什么也不会发生.



fn takes_ownership(some_string: String) {  // some_string 进入作用域
    println!("《变量与函数交互之引用类型的移动》: {some_string}");
}  // 这里，some_string 离开作用域并调用 `drop` 方法。 // 底层内存被释放。

fn makes_copy(some_integer: i32) {  // some_integer 进入作用域
    println!("《变量与函数交互之基本类型的copy》: {some_integer}");
}  // 这里，some_integer 离开作用域。不会发生任何特殊操作。
